/// Represents a geographical location in the real world.
class LocationData {
  LocationData._(
    this.latitude,
    this.longitude,
    this.accuracy,
    this.altitude,
    this.speed,
    this.speedAccuracy,
    this.heading,
    this.time,
    this.isMock,
    this.verticalAccuracy,
    this.headingAccuracy,
    this.elapsedRealtimeNanos,
    this.elapsedRealtimeUncertaintyNanos,
    this.satelliteNumber,
    this.provider,
  );

  /// Creates a new [LocationData] instance from a map.
  factory LocationData.fromMap(Map<String, dynamic> dataMap) {
    return LocationData._(
      dataMap['latitude'] as double?,
      dataMap['longitude'] as double?,
      dataMap['accuracy'] as double?,
      dataMap['altitude'] as double?,
      dataMap['speed'] as double?,
      dataMap['speed_accuracy'] as double?,
      dataMap['heading'] as double?,
      dataMap['time'] as double?,
      dataMap['isMock'] == 1,
      dataMap['verticalAccuracy'] as double?,
      dataMap['headingAccuracy'] as double?,
      dataMap['elapsedRealtimeNanos'] as double?,
      dataMap['elapsedRealtimeUncertaintyNanos'] as double?,
      dataMap['satelliteNumber'] as int?,
      dataMap['provider'] as String?,
    );
  }

  /// Latitude in degrees
  final double? latitude;

  /// Longitude, in degrees
  final double? longitude;

  /// Estimated horizontal accuracy of this location, radial, in meters
  ///
  /// Will be null if not available.
  final double? accuracy;

  /// Estimated vertical accuracy of altitude, in meters.
  ///
  /// Will be null if not available.
  final double? verticalAccuracy;

  /// In meters above the WGS 84 reference ellipsoid. Derived from GPS informations.
  ///
  /// Will be null if not available.
  final double? altitude;

  /// In meters/second
  ///
  /// Will be null if not available.
  final double? speed;

  /// In meters/second
  ///
  /// Will be null if not available.
  /// Not available on web
  final double? speedAccuracy;

  /// Heading is the horizontal direction of travel of this device, in degrees
  ///
  /// Will be null if not available.
  final double? heading;

  /// timestamp of the LocationData
  final double? time;

  /// Is the location currently mocked
  ///
  /// Always false on iOS
  final bool? isMock;

  /// Get the estimated bearing accuracy of this location, in degrees.
  /// Only available on Android
  /// https://developer.android.com/reference/android/location/Location#getBearingAccuracyDegrees()
  final double? headingAccuracy;

  /// Return the time of this fix, in elapsed real-time since system boot.
  /// Only available on Android
  /// https://developer.android.com/reference/android/location/Location#getElapsedRealtimeNanos()
  final double? elapsedRealtimeNanos;

  /// Get estimate of the relative precision of the alignment of the ElapsedRealtimeNanos timestamp.
  /// Only available on Android
  /// https://developer.android.com/reference/android/location/Location#getElapsedRealtimeUncertaintyNanos()
  final double? elapsedRealtimeUncertaintyNanos;

  /// The number of satellites used to derive the fix.
  /// Only available on Android
  /// https://developer.android.com/reference/android/location/Location#getExtras()
  final int? satelliteNumber;

  /// The name of the provider that generated this fix.
  /// Only available on Android
  /// https://developer.android.com/reference/android/location/Location#getProvider()
  final String? provider;

  @override
  String toString() =>
      'LocationData<lat: $latitude, long: $longitude${(isMock ?? false) ? ', mocked' : ''}>';

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is LocationData &&
          runtimeType == other.runtimeType &&
          latitude == other.latitude &&
          longitude == other.longitude &&
          accuracy == other.accuracy &&
          altitude == other.altitude &&
          speed == other.speed &&
          speedAccuracy == other.speedAccuracy &&
          heading == other.heading &&
          time == other.time &&
          isMock == other.isMock;

  @override
  int get hashCode =>
      latitude.hashCode ^
      longitude.hashCode ^
      accuracy.hashCode ^
      altitude.hashCode ^
      speed.hashCode ^
      speedAccuracy.hashCode ^
      heading.hashCode ^
      time.hashCode ^
      isMock.hashCode;
}

class GeoAddress {
  GeoAddress._(
      this.latitude,
      this.longitude,
      this.locale,
      this.countryCode,
      this.countryName,
      this.locality,
      this.subLocality,
      this.roadName,
      this.subRoadName,
      this.placeName,
      this.premises,
      this.postalCode,
      this.phoneNumber,
      this.addressUrl,
      );

  /// Creates a new [GeoAddress] instance from a map.
  factory GeoAddress.fromMap(Map<String, dynamic> dataMap) {
    return GeoAddress._(
      dataMap['latitude'] as double?,
      dataMap['longitude'] as double?,
      dataMap['locale'] as String?,
      dataMap['countryCode'] as String?,
      dataMap['countryName'] as String?,
      dataMap['locality'] as String?,
      dataMap['subLocality'] as String?,
      dataMap['roadName'] as String?,
      dataMap['subRoadName'] as String?,
      dataMap['placeName'] as String?,
      dataMap['premises'] as String?,
      dataMap['postalCode'] as String?,
      dataMap['phoneNumber'] as String?,
      dataMap['addressUrl'] as String?,
    );
  }

  /// Latitude in degrees
  final double? latitude;

  /// Longitude, in degrees
  final double? longitude;

  /// Indicates language used for the location description.
  final String? locale;

  /// Indicates detailed address information.
  final String? placeName;

  ///Indicates country code.
  final String? countryCode;

  /// Indicates country name.
  final String? countryName;

  /// Indicates locality information.
  final String? locality;

  /// Indicates sub-locality information.
  final String? subLocality;

  /// Indicates road name.
  final String? roadName;

  /// Indicates auxiliary road information.
  final String? subRoadName;

  /// Indicates house information.
  final String? premises;

  /// Indicates postal code.
  final String? postalCode;

  /// Indicates phone number.
  final String? phoneNumber;

  /// Indicates website URL.
  final String? addressUrl;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
          other is LocationData &&
              runtimeType == other.runtimeType &&
              latitude == other.latitude &&
              longitude == other.longitude &&
              locale == other.accuracy &&
              countryCode == other.altitude &&
              countryName == other.speed &&
              locality == other.speedAccuracy &&
              subLocality == other.heading &&
              roadName == other.time &&
              subRoadName == other.isMock;

  @override
  int get hashCode =>
      latitude.hashCode ^
      longitude.hashCode ^
      locale.hashCode ^
      countryCode.hashCode ^
      countryName.hashCode ^
      locality.hashCode ^
      subLocality.hashCode ^
      roadName.hashCode ^
      subRoadName.hashCode;
}

/// Precision of the Location. A lower precision will provide a greater battery
/// life.
///
/// https://developers.google.com/android/reference/com/google/android/gms/location/LocationRequest
/// https://developer.apple.com/documentation/corelocation/cllocationaccuracy?language=objc
enum LocationAccuracy {
  /// To request best accuracy possible with zero additional power consumption
  /// On Ohos, this is equivalent to FIRST_FIX.
  powerSave,

  /// To request "city" level accuracy
  /// On Ohos, this is equivalent to LOW_POWER.
  low,

  /// To request "block" level accuracy
  balanced,

  /// To request the most accurate locations available
  /// On Ohos, this is equivalent to ACCURACY.
  high,

  /// To request location for navigation usage (affect only iOS)
  navigation,

  /// On iOS 14.0+, this is mapped to kCLLocationAccuracyReduced.
  /// See https://developer.apple.com/documentation/corelocation/kcllocationaccuracyreduced
  ///
  /// On iOS < 14.0 and Android, this is equivalent to LocationAccuracy.low.
  reduced,

  /// On Ohos, this is equivalent to UNSET. (affect only Ohos)
  unset
}

enum LocationScenario  {
  /// To request location for default scene (affect only Ohos)
  unset,

  /// To request location for NAVIGATION scene (affect only Ohos)
  navigation,

  /// To request location for TRAJECTORY_TRACKING scene (affect only Ohos)
  trajectoryTracking,

  /// To request location for CAR_HAILING scene (affect only Ohos)
  carHailing,

  /// To request location for DAILY_LIFE_SERVICE scene (affect only Ohos)
  dailyLifeService,

  /// To request location for NO_POWER scene (affect only Ohos)
  noPower,
}

/// Status of a permission request to use location services.
enum PermissionStatus {
  /// The permission to use location services has been granted for high accuracy.
  granted,

  /// The permission has been granted but for low accuracy. Only valid on iOS 14+.
  grantedLimited,

  /// The permission to use location services has been denied by the user. May
  /// have been denied forever on iOS.
  denied,

  /// The permission to use location services has been denied forever by the
  /// user. No dialog will be displayed on permission request.
  deniedForever
}

/// The response object of `Location.changeNotificationOptions`.
///
/// Contains native information about the notification shown on Android, when
/// running in background mode.
class NotificationData {
  const NotificationData._(this.channelId, this.notificationId);

  /// Creates a new [NotificationData] instance from a map.
  factory NotificationData.fromMap(Map<dynamic, dynamic> data) {
    return NotificationData._(
      data['channelId'] as String,
      data['notificationId'] as int,
    );
  }

  /// The id of the used Android notification channel.
  final String channelId;

  /// The id of the shown Android notification.
  final int notificationId;

  @override
  String toString() =>
      'NotificationData<channelId: $channelId, notificationId: $notificationId>';

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is NotificationData &&
          runtimeType == other.runtimeType &&
          channelId == other.channelId &&
          notificationId == other.notificationId;

  @override
  int get hashCode => channelId.hashCode ^ notificationId.hashCode;
}
